﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Practices.Composite.Events;
using KinectMetroApp.ServerRegistry.Events;

namespace KinectMetroApp.ServerRegistry
{
    public class WebSocketClient
    {
        private int count = 0;

        private IEventAggregator eventAggregator = null;

        public WebSocketClient(IEventAggregator eventAggregator)
        {
            this.eventAggregator = eventAggregator;
            this.eventAggregator.GetEvent<ServerDataReceivedChangedEvent>().Subscribe(ProcessDataReceived);
        }

        private bool dataReceived = false;

        private byte[] bufferReceived = null;

        public void ProcessDataReceived(object obj)
        {
            bufferReceived = obj as byte[];
            string msg = Encoding.UTF8.GetString(bufferReceived).Replace("\0","");
            bufferReceived = Encoding.UTF8.GetBytes(msg);
            dataReceived = true;

        }

        private static WebSocket webSocket;

        //### Starting the server        
        // Using HttpListener is reasonably straightforward. Start the listener and run a loop that receives and processes incoming WebSocket connections.
        // Each iteration of the loop "asynchronously waits" for the next incoming request using the `GetContextAsync` extension method (defined below).             
        // If the request is for a WebSocket connection then pass it on to `ProcessRequest` - otherwise set the status code to 400 (bad request). 
        public async Task Start(string listenerPrefix)
        {
            HttpListener listener = new HttpListener();
            listener.Prefixes.Add(listenerPrefix);
            listener.Start();
            Console.WriteLine("Listening...");

                try
                {
                    HttpListenerContext listenerContext = await listener.GetContextAsync();
                    if (listenerContext.Request.IsWebSocketRequest)
                    {
                        WebSocketContext webSocketContext = null;
                        try
                        {
                            // When calling `AcceptWebSocketAsync` the negotiated subprotocol must be specified. This sample assumes that no subprotocol 
                            // was requested. There is a small bug here in the developer preview where string.Empty is treated as "no subprotocol". Instead it would be correct to pass null
                            // and later versions of this sample will do so.
                            webSocketContext = await listenerContext.AcceptWebSocketAsync(subProtocol: null);
                            Interlocked.Increment(ref count);
                            Console.WriteLine("Processed: {0}", count);
                        }
                        catch (Exception e)
                        {
                            // The upgrade process failed somehow. For simplicity lets assume it was a failure on the part of the server and indicate this using 500.
                            listenerContext.Response.StatusCode = 500;
                            listenerContext.Response.Close();
                            Console.WriteLine("Exception: {0}", e);
                            return;
                        }

                        webSocket = webSocketContext.WebSocket;
                        ProcessRequest(webSocket);
                    }
                    else
                    {
                        listenerContext.Response.StatusCode = 400;
                        listenerContext.Response.Close();
                    }
                }
                catch (Exception e)
                {

                    Console.WriteLine("Exception: {0}", e);
                }
            
        }

        //### Accepting WebSocket connections
        // Calling `AcceptWebSocketAsync` on the `HttpListenerContext` will accept the WebSocket connection, sending the required 101 response to the client
        // and return an instance of `WebSocketContext`. This class captures relevant information available at the time of the request and is a read-only 
        // type - you cannot perform any actual IO operations such as sending or receiving using the `WebSocketContext`. These operations can be 
        // performed by accessing the `System.Net.WebSocket` instance via the `WebSocketContext.WebSocket` property.        

        private async void ProcessRequest(WebSocket webSocket)
        {
            try
            {
                //### Receiving
                // Define a receive buffer to hold data received on the WebSocket connection. The buffer will be reused as we only need to hold on to the data
                // long enough to send it back to the sender.
                byte[] receiveBuffer = new byte[1024];

                // While the WebSocket connection remains open run a simple loop that receives data and sends it back.
                while (webSocket.State == WebSocketState.Open)
                {
                    // The first step is to begin a receive operation on the WebSocket. `ReceiveAsync` takes two parameters:
                    //
                    // * An `ArraySegment` to write the received data to. 
                    // * A cancellation token. In this example we are not using any timeouts so we use `CancellationToken.None`.
                    //
                    // `ReceiveAsync` returns a `Task<WebSocketReceiveResult>`. The `WebSocketReceiveResult` provides information on the receive operation that was just 
                    // completed, such as:                
                    //
                    // * `WebSocketReceiveResult.MessageType` - What type of data was received and written to the provided buffer. Was it binary, utf8, or a close message?                
                    // * `WebSocketReceiveResult.Count` - How many bytes were read?                
                    // * `WebSocketReceiveResult.EndOfMessage` - Have we finished reading the data for this message or is there more coming?
                    WebSocketReceiveResult receiveResult = await webSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);

                    // The WebSocket protocol defines a close handshake that allows a party to send a close frame when they wish to gracefully shut down the connection.
                    // The party on the other end can complete the close handshake by sending back a close frame.
                    //
                    // If we received a close frame then lets participate in the handshake by sending a close frame back. This is achieved by calling `CloseAsync`. 
                    // `CloseAsync` will also terminate the underlying TCP connection once the close handshake is complete.
                    //
                    // The WebSocket protocol defines different status codes that can be sent as part of a close frame and also allows a close message to be sent. 
                    // If we are just responding to the client's request to close we can just use `WebSocketCloseStatus.NormalClosure` and omit the close message.
                    if (receiveResult.MessageType == WebSocketMessageType.Close)
                    {
                        await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                    }
                    // This echo server can't handle text frames so if we receive any we close the connection with an appropriate status code and message.
                    else if (receiveResult.MessageType == WebSocketMessageType.Text)
                    {
                        await webSocket.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "Cannot accept text frame", CancellationToken.None);
                    }
                    // Otherwise we must have received binary data. Send it back by calling `SendAsync`. Note the use of the `EndOfMessage` flag on the receive result. This
                    // means that if this echo server is sent one continuous stream of binary data (with EndOfMessage always false) it will just stream back the same thing.
                    // If binary messages are received then the same binary messages are sent back.
                    else
                    {
                        //todo: traitment will be here
                        //if (receiveBuffer == new byte[] { 1 })
                        //{

                        while (true)
                        {
                            while (!dataReceived)
                            {
                                Thread.Sleep(200);
                            }
                            dataReceived = false;
                            byte[] resultBuffer = this.bufferReceived;
                            await webSocket.SendAsync(new ArraySegment<byte>(resultBuffer), WebSocketMessageType.Binary, false, CancellationToken.None);  
                        }
                        //}
                    }

                    // The echo operation is complete. The loop will resume and `ReceiveAsync` is called again to wait for the next data frame.
                }
            }
            catch (Exception e)
            {
                // Just log any exceptions to the console. Pretty much any exception that occurs when calling `SendAsync`/`ReceiveAsync`/`CloseAsync` is unrecoverable in that it will abort the connection and leave the `WebSocket` instance in an unusable state.
                Console.WriteLine("Exception: {0}", e);
            }
            finally
            {
                // Clean up by disposing the WebSocket once it is closed/aborted.
                if (webSocket != null)
                    webSocket.Dispose();
            }
        }
    }
}
